#include <libwebsockets.h>
#include <string.h>
#include <signal.h>
#include <assert.h>

#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libavdevice/avdevice.h>
#include <libavutil/imgutils.h>
#define VIDEO_FILE_PATH "source/test.mp4"
int image_send(void *wsi, uint8_t *src, size_t size)
{
  printf("%s start\n", __func__);
//  static uint8_t buf[1920*1080 + LWS_PRE];
  uint8_t *buf = calloc(1, 1920*1080 + LWS_PRE);
  //uint8_t buf[size + LWS_PRE + 1024];
  int n = size, m;
  //int n = size + LWS_PRE, m;

  //if (lws_send_pipe_attached(wsi)) {
    memcpy(buf + LWS_PRE, src, size);
    m = lws_write(wsi, buf + LWS_PRE, n, LWS_WRITE_BINARY);
    if (m < n) {
      lwsl_err("ERROR %d writing to ws socket\n", m);
      return -1;
    }
  printf("end size %ld\n", size);
  //}
  return 0;
}
void ffmpeg_open(char *node, void *user_data)
{
  printf("%s start\n", __func__);
  int ret;
  const char *device = node;

  avdevice_register_all();
  avformat_network_init();
  AVFormatContext *formatContext = NULL;
  if (avformat_open_input(&formatContext, device, NULL, NULL) < 0) {
    printf("Failed to open camera device.\n");
    return;
  }

  if (avformat_find_stream_info(formatContext, NULL) < 0) {
    printf("Failed to find stream information.\n");
    avformat_close_input(&formatContext);
    return;
  }

  int videoStreamIndex = -1;
  for (int i = 0; i < formatContext->nb_streams; i++) {
    if (formatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
      videoStreamIndex = i;
      break;
    }
  }

  if (videoStreamIndex == -1) {
    printf("Failed to find video stream.\n");
    avformat_close_input(&formatContext);
    return;
  }

  // 获取视频流的解码器
  AVCodecParameters *codecParams = formatContext->streams[videoStreamIndex]->codecpar;
  const AVCodec *codec = avcodec_find_decoder(codecParams->codec_id);
  if (!codec) {
    printf("Failed to find codec.\n");
    avformat_close_input(&formatContext);
    return;
  }

  AVCodecContext *codecContext = avcodec_alloc_context3(codec);
  if (avcodec_parameters_to_context(codecContext, codecParams) < 0) {
    printf("Failed to copy codec parameters to context.\n");
    avformat_close_input(&formatContext);
    avcodec_free_context(&codecContext);
    return;
  }

  if (avcodec_open2(codecContext, codec, NULL) < 0) {
    printf("Failed to open codec.\n");
    avformat_close_input(&formatContext);
    avcodec_free_context(&codecContext);
    return;
  }

  // 初始化图像转换器
  struct SwsContext *swsContext = sws_getContext(
      codecContext->width, codecContext->height, codecContext->pix_fmt,
      codecContext->width, codecContext->height,
      AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
//encoder
  const AVCodec *encoder = NULL;
  if (!(encoder = avcodec_find_encoder(AV_CODEC_ID_H264))) {
    fprintf(stderr, "Codec not found\n");
    ret = -1;
    goto err;
  }
  AVCodecContext *c;
  if (!(c = avcodec_alloc_context3(encoder))) {
    fprintf(stderr, "Could not allocate video codec context\n");
    ret = -1;
    goto err;
  }
  c->width = codecContext->width;
  c->height = codecContext->height;
  c->pix_fmt = AV_PIX_FMT_YUV420P;
  c->time_base = (AVRational){1, 30}; // 设置为30fps
  if ((ret = avcodec_open2(c, encoder, NULL)) < 0) {
    fprintf(stderr, "Could not open encoder\n");
    goto err;
  }
  AVFrame *frame = av_frame_alloc();
  if (!frame) {
    fprintf(stderr, "Could not allocate video frame\n");
    goto err;
  }
  /*
  frame->format = c->pix_fmt;
  frame->width = c->width;
  frame->height = c->height;
  if ((ret = av_frame_get_buffer(frame, 32)) < 0) {
    fprintf(stderr, "Could not allocate the video frame data\n");
    goto err;
  } 
  */
  AVPacket packet;
  while (av_read_frame(formatContext, &packet) >= 0) {
    if (packet.stream_index == videoStreamIndex) {
      if (avcodec_send_packet(codecContext, &packet) < 0) {
        printf("Error sending packet to codec.\n");
        break;
      }
      while ((ret = avcodec_receive_frame(codecContext, frame)) >= 0) {
        frame->pts = frame->best_effort_timestamp;
        AVFrame *swsFrame = av_frame_alloc();
        av_image_alloc(swsFrame->data, swsFrame->linesize,
            codecContext->width, codecContext->height,
            AV_PIX_FMT_YUV420P, 1);
        ret = sws_scale(swsContext, (const uint8_t * const*)frame->data,
            frame->linesize, 0, codecContext->height,
            swsFrame->data, swsFrame->linesize);
        if (ret < 0) {
          printf("ret:%s\n", strerror(-ret));
          fprintf(stderr, "Error sws\n");
          goto err;
        }
        swsFrame->format = c->pix_fmt;
        swsFrame->width = c->width;
        swsFrame->height = c->height;
        if ((ret = av_frame_get_buffer(swsFrame, 32)) < 0) {
          fprintf(stderr, "Could not allocate the video frame data\n");
          goto err;
        }
        swsFrame->pts = frame->pts;
        printf("pts:%ld\n", swsFrame->pts);
        //swsFrame->time_base = av_buffersink_get_time_base(0);
        //swsFrame->pict_type = AV_PICTURE_TYPE_NONE;
        if (swsFrame && swsFrame->pts != AV_NOPTS_VALUE)
          swsFrame->pts = av_rescale_q(swsFrame->pts,
              swsFrame->time_base, c->time_base);
        if ((ret = avcodec_send_frame(c, swsFrame)) < 0) {
          printf("ret:%s\n", strerror(-ret));
          fprintf(stderr, "Error sending a frame for encoding\n");
          goto err;
        }
        size_t image_data_size = c->width * c->height * 3;
        image_send(user_data, swsFrame->data[0], image_data_size);

        printf("%d\n", __LINE__);
        av_frame_free(&frame);
        //sleep(1);
        break;
      }
    }
    av_packet_unref(&packet);
    goto err;
  }
err:
  avformat_close_input(&formatContext);
  avcodec_free_context(&codecContext);
  sws_freeContext(swsContext);
  av_frame_free(&frame);
}
int websocket_send_file(void *wsi, char *filename)
{
  FILE *video_file = fopen(filename, "rb");
  if (!video_file) {
      fprintf(stderr, "Failed to open video file\n");
      return -1;
  }

  unsigned char buffer[4096+LWS_PRE];
  size_t bytes_read,m, i = 0;
  while ((bytes_read = fread(buffer+LWS_PRE, 1, sizeof(buffer)-LWS_PRE,
          video_file)) > 0) {
    m = lws_write(wsi, buffer + LWS_PRE, bytes_read, LWS_WRITE_BINARY);
    if (m < bytes_read) {
      lwsl_err("ERROR %ld writing to ws socket\n", m);
      return -1;
    }
    usleep(100000);
    i++;
    printf("send:%ld %ld\n", i, bytes_read);
    /*
    if (i == 2)
      break;
      */
  }
  fclose(video_file);
  return 0;
}
//#define DEBUG_TEST
static int
callback_minimal_server_echo(struct lws *wsi, enum lws_callback_reasons reason,
			  void *user, void *in, size_t len)
{
#ifdef DEBUG_TEST
    int n = 921600, m;
    uint8_t buf[n + LWS_PRE];
#endif
    //size_t size = 1920*1080;
    //uint8_t *src = calloc(1, size);
	switch (reason) {

	case LWS_CALLBACK_PROTOCOL_INIT:
		lwsl_warn("LWS_CALLBACK_PROTOCOL_INIT\n");
		break;

	case LWS_CALLBACK_ESTABLISHED:
		lwsl_warn("LWS_CALLBACK_ESTABLISHED\n");
    websocket_send_file(wsi, VIDEO_FILE_PATH);
		//lws_callback_on_writable(wsi);
		break;

	case LWS_CALLBACK_SERVER_WRITEABLE:

		lwsl_user("LWS_CALLBACK_SERVER_WRITEABLE\n");

#ifdef DEBUG_TEST
    /*
    int n = lws_snprintf((char *)buf + LWS_PRE, sizeof(buf) - LWS_PRE,
        "hello:%d", __LINE__);
        */
    //int m = lws_write(wsi, buf + LWS_PRE, n, LWS_WRITE_TEXT);
    m = lws_write(wsi, buf + LWS_PRE, n, LWS_WRITE_BINARY);
    if (m < n) {
      lwsl_err("ERROR %d writing to ws socket\n", m);
      return -1;
    }
    printf("send:%s %d\n", buf+LWS_PRE, n);
#else
    ffmpeg_open("/dev/video0", wsi);
#endif
    //while (1) {
    //  image_send(wsi, src, size);
     // sleep(1);
    //}
		//lws_callback_on_writable(wsi);
		break;

	case LWS_CALLBACK_RECEIVE:

		lwsl_user("LWS_CALLBACK_RECEIVE: %4d:%s\n", (int)len, (char *)in);
		lws_callback_on_writable(wsi);
		break;

	case LWS_CALLBACK_CLOSED:
		lwsl_user("LWS_CALLBACK_CLOSED\n");
		break;

	default:
		break;
	}

	return 0;
}
static struct lws_protocols protocols[] = {
  {
		"lws-minimal-server-echo",
		callback_minimal_server_echo,
		1920*1080,
		1024, 
		0, NULL, 0 },
	LWS_PROTOCOL_LIST_TERM
};

static int interrupted, port = 7681, options;

/* pass pointers to shared vars to the protocol */

static const struct lws_protocol_vhost_options pvo_options = {
	NULL,
	NULL,
	"options",		/* pvo name */
	(void *)&options	/* pvo value */
};

static const struct lws_protocol_vhost_options pvo_interrupted = {
	&pvo_options,
	NULL,
	"interrupted",		/* pvo name */
	(void *)&interrupted	/* pvo value */
};

static const struct lws_protocol_vhost_options pvo = {
	NULL,				/* "next" pvo linked-list */
	&pvo_interrupted,		/* "child" pvo linked-list */
	"lws-minimal-server-echo",	/* protocol name we belong to on this vhost */
	""				/* ignored */
};
static const struct lws_extension extensions[] = {
	{
		"permessage-deflate",
		lws_extension_callback_pm_deflate,
		"permessage-deflate"
		 "; client_no_context_takeover"
		 "; client_max_window_bits"
	},
	{ NULL, NULL, NULL /* terminator */ }
};

void sigint_handler(int sig)
{
	interrupted = 1;
}

int main(int argc, const char **argv)
{
	struct lws_context_creation_info info;
	struct lws_context *context;
	const char *p;
	int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE;

	signal(SIGINT, sigint_handler);

	if ((p = lws_cmdline_option(argc, argv, "-d")))
		logs = atoi(p);

	lws_set_log_level(logs, NULL);
	lwsl_user("LWS minimal ws client echo + permessage-deflate + multifragment bulk message\n");
	lwsl_user("   lws-minimal-ws-client-echo [-n (no exts)] [-p port] [-o (once)]\n");


	if ((p = lws_cmdline_option(argc, argv, "-p")))
		port = atoi(p);

	if (lws_cmdline_option(argc, argv, "-o"))
		options |= 1;

	memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
	info.port = port;
	info.protocols = protocols;
	info.pvo = &pvo;
  info.extensions = NULL;
	if (!lws_cmdline_option(argc, argv, "-n"))
		info.extensions = extensions;
	//info.pt_serv_buf_size = 32 * 1024;
	info.pt_serv_buf_size = 1920*1080+ 1024;
	info.options = LWS_SERVER_OPTION_VALIDATE_UTF8 |
		LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;

	context = lws_create_context(&info);
	if (!context) {
		lwsl_err("lws init failed\n");
		return 1;
	}

	while (n >= 0 && !interrupted)
		n = lws_service(context, 0);

	lws_context_destroy(context);

	lwsl_user("Completed %s\n", interrupted == 2 ? "OK" : "failed");

	return interrupted != 2;
}
